#include <thread>
#include <stdx/all.h>
#include <clib/netx.h>
#include <unordered_map>
#include <openssl/SSLSocket.h>

static string GetTemplateConfigString()
{
	u_char data[] = {
		0x2D,0x2D,0x20,0xE7,0x9B,0x91,0xE5,0x90,0xAC,0xE7,0xAB,0xAF,0xE5,0x8F,0xA3,0x0A,
		0x50,0x4F,0x52,0x54,0x20,0x3D,0x20,0x35,0x35,0x35,0x35,0x0A,0x0A,0x2D,0x2D,0x20,
		0xE4,0xB8,0xBB,0xE6,0x9C,0xBA,0xE5,0x9C,0xB0,0xE5,0x9D,0x80,0x0A,0x48,0x4F,0x53,
		0x54,0x20,0x3D,0x20,0x22,0x30,0x2E,0x30,0x2E,0x30,0x2E,0x30,0x22,0x0A,0x0A,0x2D,
		0x2D,0x20,0xE6,0x9C,0x80,0xE5,0x90,0xA6,0xE4,0xB8,0xBA,0x53,0x53,0x4C,0xE8,0xBF,
		0x9E,0xE6,0x8E,0xA5,0xE8,0xBD,0xAC,0xE5,0x8F,0x91,0x0A,0x53,0x53,0x4C,0x5F,0x45,
		0x4E,0x41,0x42,0x4C,0x45,0x44,0x20,0x3D,0x20,0x66,0x61,0x6C,0x73,0x65,0x0A,0x0A,
		0x2D,0x2D,0x20,0x50,0x45,0x4D,0xE8,0xAF,0x81,0xE4,0xB9,0xA6,0xE6,0x96,0x87,0xE4,
		0xBB,0xB6,0x0A,0x43,0x45,0x52,0x54,0x5F,0x46,0x49,0x4C,0x45,0x50,0x41,0x54,0x48,
		0x20,0x3D,0x20,0x22,0x63,0x65,0x72,0x74,0x2E,0x63,0x72,0x74,0x22,0x0A,0x0A,0x2D,
		0x2D,0x20,0x50,0x45,0x4D,0xE7,0xA7,0x81,0xE9,0x92,0xA5,0xE6,0x96,0x87,0xE4,0xBB,
		0xB6,0x0A,0x50,0x52,0x49,0x4B,0x45,0x59,0x5F,0x46,0x49,0x4C,0x45,0x50,0x41,0x54,
		0x48,0x20,0x3D,0x20,0x22,0x63,0x65,0x72,0x74,0x2E,0x6B,0x65,0x79,0x22,0x0A,0x0A,
		0x2D,0x2D,0x20,0xE7,0x9B,0xAE,0xE7,0x9A,0x84,0xE5,0x9C,0xB0,0xE5,0x9D,0x80,0xE5,
		0x88,0x97,0xE8,0xA1,0xA8,0x28,0xE5,0x9C,0xB0,0xE5,0x9D,0x80,0x3A,0xE7,0xAB,0xAF,
		0xE5,0x8F,0xA3,0x3A,0xE6,0x9D,0x83,0xE9,0x87,0x8D,0x7C,0xE5,0x9C,0xB0,0xE5,0x9D,
		0x80,0x3A,0xE7,0xAB,0xAF,0xE5,0x8F,0xA3,0x3A,0xE6,0x9D,0x83,0xE9,0x87,0x8D,0x29,
		0x0A,0x44,0x45,0x53,0x54,0x5F,0x48,0x4F,0x53,0x54,0x5F,0x4C,0x49,0x53,0x54,0x20,
		0x3D,0x20,0x22,0x31,0x32,0x37,0x2E,0x30,0x2E,0x30,0x2E,0x31,0x3A,0x38,0x38,0x38,
		0x38,0x22,0x0A,0x0A,0x2D,0x2D,0x20,0xE5,0x9C,0xB0,0xE5,0x9D,0x80,0xE7,0x99,0xBD,
		0xE5,0x90,0x8D,0xE5,0x8D,0x95,0x28,0xE5,0x9C,0xB0,0xE5,0x9D,0x80,0x7C,0xE5,0x9C,
		0xB0,0xE5,0x9D,0x80,0x29,0x0A,0x57,0x48,0x49,0x54,0x45,0x5F,0x48,0x4F,0x53,0x54,
		0x5F,0x4C,0x49,0x53,0x54,0x20,0x3D,0x20,0x22,0x22,0x0A,0x0A,0x2D,0x2D,0x20,0xE5,
		0x9C,0xB0,0xE5,0x9D,0x80,0xE9,0xBB,0x91,0xE5,0x90,0x8D,0xE5,0x8D,0x95,0x28,0xE5,
		0x9C,0xB0,0xE5,0x9D,0x80,0x7C,0xE5,0x9C,0xB0,0xE5,0x9D,0x80,0x29,0x0A,0x42,0x4C,
		0x41,0x43,0x4B,0x5F,0x48,0x4F,0x53,0x54,0x5F,0x4C,0x49,0x53,0x54,0x20,0x3D,0x20,
		0x22,0x22,0x0A,0x0A,0x2D,0x2D,0x20,0xE5,0x90,0x8C,0xE4,0xB8,0x80,0xE5,0x9C,0xB0,
		0xE5,0x9D,0x80,0xE6,0xAF,0x8F,0xE5,0x88,0x86,0xE9,0x92,0x9F,0xE8,0xBF,0x9E,0xE6,
		0x8E,0xA5,0xE4,0xB8,0x8A,0xE9,0x99,0x90,0x0A,0x41,0x44,0x44,0x52,0x45,0x53,0x53,
		0x5F,0x43,0x4F,0x4E,0x4E,0x45,0x43,0x54,0x5F,0x50,0x45,0x52,0x4D,0x49,0x4E,0x20,
		0x3D,0x20,0x31,0x30,0x30,0x30,0x0A,0x0A,0x2D,0x2D,0x20,0xE6,0x97,0xA5,0xE5,0xBF,
		0x97,0xE5,0xAD,0x98,0xE6,0x94,0xBE,0xE7,0x9B,0xAE,0xE5,0xBD,0x95,0x0A,0x4C,0x4F,
		0x47,0x46,0x49,0x4C,0x45,0x5F,0x50,0x41,0x54,0x48,0x20,0x3D,0x20,0x22,0x6C,0x6F,
		0x67,0x22,0x0A,0x0A,0x2D,0x2D,0x20,0xE5,0x8D,0x95,0xE4,0xB8,0xAA,0xE6,0x97,0xA5,
		0xE5,0xBF,0x97,0xE6,0x96,0x87,0xE4,0xBB,0xB6,0xE6,0x9C,0x80,0xE5,0xA4,0xA7,0xE5,
		0xA4,0xA7,0xE5,0xB0,0x8F,0x28,0x4B,0x42,0x29,0x0A,0x4C,0x4F,0x47,0x46,0x49,0x4C,
		0x45,0x5F,0x4D,0x41,0x58,0x53,0x49,0x5A,0x45,0x20,0x3D,0x20,0x31,0x30,0x30,0x30,
		0x30,0x00
	};

	return (char*)(data);
}

class MainApplication : public Application
{
	struct SSLWorkItem : public WorkItem
	{
		static const int DELAY = 1;
		static const int TIMEOUT = 3;
		static const int BUFFER_MAXSIZE = 8 * 1024;

		int res;
		int readed;
		time_t ctime;
		time_t utime;
		string client;
		sp<Socket> src;
		sp<Socket> dest;
		sp<Socket> sock;
		sp<SSLSocket> ssl;
		MainApplication* app;
		char rspdata[BUFFER_MAXSIZE];

		void run()
		{
			SOCKET conn = sock->getHandle();

			if (res == XG_TIMEOUT)
			{
				if (ServerSocketAttach(conn, client.c_str()))
				{
					utime = time(NULL);
				}
				else
				{
					LogTrace(eIMP, "connect[%s] shutdown", client.c_str());

					app->close(conn);
				}
			}
			else
			{
				LogTrace(eIMP, "connect[%s] shutdown", client.c_str());

				app->close(conn);
			}
		}
		bool init()
		{
			sock->setSendTimeout(DELAY);
			sock->setRecvTimeout(DELAY);

			if (app->sslenable)
			{
				ssl = newsp<SSLSocket>();
				ssl->setCloseFlag(false);

				CHECK_FALSE_RETURN(ssl->init(sock->getHandle(), app->context));
			}
			else
			{
				src = sock;
			}

			return true;
		}
		int process()
		{
			int val = response();
			
			if (val >= 0 && readed == 0)
			{
				val = ssl ? src->tryCheck(0) : XG_OK;

				if (val > 0)
				{
					char buffer[BUFFER_MAXSIZE];

					if ((val = src->read(buffer, sizeof(buffer), false)) < 0) return val;

					if (val > 0)
					{
						if ((val = dest->write(buffer, val)) < 0) return val;
					}
					else
					{
						val = XG_TIMEOUT;
					}
				}

				if (val == XG_TIMEOUT)
				{
					if ((val = dest->read(rspdata, sizeof(rspdata), false)) < 0) return val;
					
					if ((readed = val) > 0)
					{
						val = response();
					}
					else
					{
						val = XG_TIMEOUT;
					}
				}
			}

			return val ? val : XG_TIMEOUT;
		}
		int response()
		{
			if (readed == 0) return readed;

			int writed = src->write(rspdata, readed, false);
			
			if (writed > 0)
			{
				readed -= writed;

				if (readed > 0) memmove(rspdata, rspdata + writed, readed);
			}

			return writed ? writed : XG_TIMEOUT;
		}
		bool runnable()
		{
			if (dest)
			{
				if (src)
				{
					if ((res = process()) == XG_TIMEOUT) return utime + app->getTimeout() < time(NULL);

					if (res > SOCKET_TIMEOUT_LIMITSIZE) utime = time(NULL);
				}
				else
				{
					if (ssl->accept() < 0)
					{
						if (utime + 3 < time(NULL))
						{
							res = XG_NETERR;
						}
						else
						{
							res = XG_TIMEOUT;
						}
					}
					else
					{
						res = XG_OK;

						src = ssl;
					}
				}
				
				return res < 0;
			}
			
			if (app->enableSSH())
			{
				if (dest = app->getSSHSocket(client.c_str()))
				{
					dest->setSendTimeout(DELAY);
					dest->setRecvTimeout(DELAY);

					return false;
				}

				if (ctime + 5 > time(NULL))
				{
					Sleep(1);

					return false;
				}
	
				res = XG_SYSBUSY;

				return true;
			}

			dest = newsp<Socket>();

			const HostItem& host = app->hostvec[abs(rand()) % app->hostvec.size()];

			if (dest->connect(host.host, host.port))
			{
				dest->setSendTimeout(DELAY);
				dest->setRecvTimeout(DELAY);
			
				return false;
			}
			
			res = XG_NETERR;

			return true;
		}
		SSLWorkItem(MainApplication* app, sp<Socket> sock, const string& client)
		{
			res = 0;
			readed = 0;
			ctime = utime = time(NULL);
			
			this->app = app;
			this->sock = sock;
			this->client = client;
		}
	};

private:
	int port;
	int timeout;
	string host;
	string keyssh;
	string hostssh;
	ConfigFile cfg;
	vector<HostItem> hostvec;
	TSMap<SOCKET, sp<SSLWorkItem>> taskmap;

	bool sslenable;
	string keyfile;
	string certfile;
	string chipherlst;
	SpinMutex sshmapmtx;
	SSLContext* context;
	map<SOCKET, time_t> sshmap;

public:
	int getTimeout()
	{
		return timeout;
	}
	bool enableSSH()
	{
		return keyssh.length() > 0 || hostssh.length() > 0;
	}

public:
	static int ProcessRequest(SOCKET conn, stConnectData* data)
	{
		sp<SSLWorkItem> item;
		static MainApplication* app = (MainApplication*)(Process::GetApplication());

		CATCH_EXCEPTION({
			if (app->taskmap.get(conn, item)) return app->push(item) ? XG_DETACHCONN : XG_SYSBUSY;
		});
		
		return XG_SYSERR;
	}
	static int ProcessConnectClosed(SOCKET conn, stConnectData* data)
	{
		static MainApplication* app = (MainApplication*)(Process::GetApplication());

		CATCH_EXCEPTION({
			app->taskmap.remove(conn);

			LogTrace(eIMP, "connect[%s] shutdown", data->addr);
		});

		return XG_OK;
	}
	static int ProcessConnect(SOCKET conn, stConnectData* data, const char* host, int port)
	{
		static MainApplication* app = (MainApplication*)(Process::GetApplication());

		CATCH_EXCEPTION({
			if (!app->checkHost(data->addr)) return XG_AUTHFAIL;

			LogTrace(eIMP, "connect[%s] success", data->addr);

			if (app->checkSSHClient(conn, data->addr)) return XG_DETACHCONN;

			sp<SSLWorkItem> item = app->createWorkItem(conn, data);

			return item && app->push(item) ? XG_DETACHCONN : XG_SYSBUSY;
		});

		return XG_SYSERR;
	}

private:
	void printTips() const
	{
		puts("  tcprouter tips");
		puts("-------------------------------------");
		puts("  version : 1.0.0");
		puts("  command : tcprouter configure");
		puts("-------------------------------------");
		puts("  -l      : print console message");
		puts("  -etc    : export template configure");
		puts("-------------------------------------");
		puts("");
	}
	void close(SOCKET conn)
	{
		taskmap.remove(conn);

		if (stdx::delay(3000, [conn](){
			SocketClose(conn);
		})) return;

		SocketClose(conn);
	}
	bool push(sp<SSLWorkItem> item)
	{
		item->utime = time(NULL);

		return stdx::async(item);
	}
	bool loadConfig()
	{
		string str;

		if (cfg.getVariable("DEST_HOST_LIST", str))
		{
			hostvec.clear();

			vector<string> vec;

			stdx::split(vec, str, "|");

			for (auto& item : vec)
			{
				vector<string> tmp;

				item = stdx::trim(item);
				stdx::split(tmp, item, ":");

				if (tmp.size() > 0 || tmp[0].length() > 0)
				{
					int weight = 1;
					HostItem host(tmp[0], port);

					if (tmp.size() > 1) host.port = stdx::atoi(tmp[1].c_str());
					if (tmp.size() > 2) weight = stdx::atoi(tmp[2].c_str());

					if (host.port > 0 || weight > 0)
					{
						if (weight > 1000) weight = 1000;

						for (int i = 0; i < weight; i++)
						{
							hostvec.push_back(host);
						}
					}
				}
			}

			CHECK_FALSE_RETURN(hostvec.size() > 0);
		}

		return true;
	}
	bool init(const char* filepath)
	{
		CHECK_FALSE_RETURN(cfg.init(filepath));

		port = 0;
		timeout = 60;
		context = NULL;
		sslenable = false;

		cfg.getVariable("SSH_KEY", keyssh);
		cfg.getVariable("SSH_HOST", hostssh);
		cfg.getVariable("SSL_ENABLED", sslenable);

		if (keyssh.length() > 0) keyssh = "SSHROUTER@" + keyssh;

		if (sslenable)
		{
			cfg.getVariable("CIPHER_LIST", chipherlst);
			cfg.getVariable("CERT_FILEPATH", certfile);
			cfg.getVariable("PRIKEY_FILEPATH", keyfile);
		}

		cfg.getVariable("PORT", port);
		cfg.getVariable("HOST", host);
		cfg.getVariable("CONNECT_TIMEOUT", timeout);

		CHECK_FALSE_RETURN(timeout > 0 && port > 0);
		CHECK_FALSE_RETURN(loadConfig());

		int sz = 0;
		string path;

		cfg.getVariable("LOGFILE_PATH", path);
		
		if (path.empty()) path = "./";

		if (cfg.getVariable("LOGFILE_MAXSIZE", sz)) sz *= 1024;

		if (sz < 1024) sz = 10 * 1024 * 1024;

		return LogThread::Instance()->init(path, sz);
	}
	sp<Socket> getSSHSocket(const char* client)
	{
		auto check = [&](){
			SpinLocker lk(sshmapmtx);

			auto it = sshmap.begin();

			if (it == sshmap.end()) return sp<Socket>();

			sp<Socket> sock = newsp<Socket>(it->first);

			sshmap.erase(it);

			return sock;
		};

		sp<Socket> sock = check();

		if (sock)
		{
			char buffer[0xFF];
				
			if (sock->write("PING", 4) > 0 && sock->read(buffer, 4) > 0) return sock;
		}

		return NULL;
	}
	bool checkSSHClient(SOCKET sock, const char* client)
	{
		CHECK_FALSE_RETURN(enableSSH());

		if (hostssh.length() > 0)
		{
			CHECK_FALSE_RETURN(strstr(client, hostssh.c_str()) == client);
			CHECK_FALSE_RETURN(client[hostssh.length()] == ':');
		}

		char buffer[4096];

		SocketSetSendTimeout(sock, SOCKECT_SENDTIMEOUT);
		SocketSetRecvTimeout(sock, SOCKECT_RECVTIMEOUT);

		for (int i = 0; i < 100; i++)
		{
			int len = SocketPeek(sock, buffer, sizeof(buffer));

			if (len < 0) return false;

			if (len < keyssh.length())
			{
				if (memcmp(buffer, keyssh.c_str(), len)) return false;
			}
			else
			{
				if (memcmp(buffer, keyssh.c_str(), keyssh.length())) return false;

				if (SocketRead(sock, buffer, keyssh.length()) < 0) return false;

				if (SocketWrite(sock, "SUCCESS", 7) < 0) return false;

				SpinLocker lk(sshmapmtx);

				if (sshmap.size() > 10)
				{
					for (auto item : sshmap) SocketClose(item.first);

					sshmap.clear();
				}

				sshmap[sock] = time(NULL);

				return true;
			}
		}

		return false;
	}
	bool checkHost(const char* client)
	{
		time_t now = time(NULL);
		static time_t utime = 0;
		static int connpermin = 0;
		static set<int> whitehostset;
		static set<int> blackhostset;
		int key = GetHostInteger(client);

		auto loadHostList = [&](){
			string str;
			static time_t utime = 0;
			static set<string> whitefileset;
			static set<string> blackfileset;
			static vector<string> whitehostlist;
			static vector<string> blackhostlist;
			static map<string, set<int>> whitehostmap;
			static map<string, set<int>> blackhostmap;

			if (path::mtime(cfg.getFilePath()) < utime)
			{
				whitehostset.clear();
				blackhostset.clear();
			}
			else
			{
				CHECK_FALSE_RETURN(cfg.reload() && loadConfig());

				connpermin = 0;
				whitehostset.clear();
				blackhostset.clear();
				whitefileset.clear();
				blackfileset.clear();
				whitehostmap.clear();
				blackhostmap.clear();
				whitehostlist.clear();
				blackhostlist.clear();

				cfg.getVariable("ADDRESS_CONNECT_PERMIN", connpermin);

				if (cfg.getVariable("WHITE_HOST_LIST", str)) stdx::split(whitehostlist, str, "|");
				if (cfg.getVariable("BLACK_HOST_LIST", str)) stdx::split(blackhostlist, str, "|");
			}

			for (string& item : whitehostlist)
			{
				item = stdx::trim(item);

				if (Socket::IsHostString(item))
				{
					whitehostset.insert(GetHostInteger(item.c_str()));
				}
				else
				{
					auto it = whitefileset.find(item);
					bool up = it == whitefileset.end() || path::mtime(item) >= utime;

					if (up && stdx::GetFileContent(str, item) >= 0)
					{
						vector<string> vec;
						set<int>& hostset = whitehostmap[item];

						hostset.clear();

						stdx::split(vec, stdx::replace(str, "\n", "|"), "|");

						for (string& item : vec)
						{
							item = stdx::trim(item);

							if (Socket::IsHostString(item))
							{
								hostset.insert(GetHostInteger(item.c_str()));
							}
						}

						LogTrace(eIMP, "load host config[%s] success", item.c_str());

						whitefileset.insert(item);
					}
				}
			}

			for (auto& item : whitehostmap)
			{
				for (int host : item.second) whitehostset.insert(host);
			}

			for (string& item : blackhostlist)
			{
				item = stdx::trim(item);

				if (Socket::IsHostString(item))
				{
					blackhostset.insert(GetHostInteger(item.c_str()));
				}
				else
				{
					auto it = blackfileset.find(item);
					bool up = it == blackfileset.end() || path::mtime(item) >= utime;

					if (up && stdx::GetFileContent(str, item) >= 0)
					{
						vector<string> vec;
						set<int>& hostset = blackhostmap[item];

						hostset.clear();

						stdx::split(vec, stdx::replace(str, "\n", "|"), "|");

						for (string& item : vec)
						{
							item = stdx::trim(item);

							if (Socket::IsHostString(item))
							{
								hostset.insert(GetHostInteger(item.c_str()));
							}
						}

						LogTrace(eIMP, "load host config[%s] success", item.c_str());

						blackfileset.insert(item);
					}
				}
			}

			for (auto& item : blackhostmap)
			{
				for (int host : item.second) blackhostset.insert(host);
			}

			utime = now;

			return true;
		};

		if (utime + 5 < now)
		{
			loadHostList();
			utime = now;
		}

		if (whitehostset.size() > 0 && whitehostset.find(key) == whitehostset.end())
		{
			LogTrace(eERR, "client[%s] unbelievable", client);
			
			return false;
		}

		if (blackhostset.size() > 0 && blackhostset.find(key) != blackhostset.end())
		{
			LogTrace(eERR, "client[%s] unbelievable", client);
			
			return false;
		}

		if (connpermin > 0)
		{
			static unordered_map<long long, int> countmap;

			now /= 60;

			if (countmap.size() > 100000)
			{
				auto it = countmap.begin();

				while (it != countmap.end())
				{
					if (now == it->first >> 32)
					{
						++it;
					}
					else
					{
						countmap.erase(it++);
					}
				}
			}

			long long tmp = key;
			auto it = countmap.find(tmp += now << 32);

			if (it == countmap.end())
			{
				countmap[tmp] = 0;
			}
			else
			{
				int& num = it->second;

				if (num > connpermin)
				{
					LogTrace(eERR, "client[%s] connect frequently", client);
					
					return false;
				}

				++num;
			}
		}

		return true;
	}
	sp<SSLWorkItem> createWorkItem(SOCKET conn, stConnectData* data)
	{
		sp<Socket> sock = newsp<Socket>();

		sock->init(conn);
		sock->setCloseFlag(false);

		sp<SSLWorkItem> item = newsp<SSLWorkItem>(this, sock, data->addr);

		if (item->init())
		{
			taskmap.set(conn, item);

			return item;
		}
		
		return NULL;
	}

public:
	bool main()
	{
		const char* path = NULL;

		Process::SetCommonExitSignal();

		if (GetCmdParamCount() <= 1 || GetCmdParam("?") || GetCmdParam("--help"))
		{
			printTips();

			return true;
		}

		if ((path = GetCmdParam("-etc")))
		{
			if (*path)
			{
				if (GetCmdParam("-i") && path::size(path) > 0)
				{
					CHECK_FALSE_RETURN(cmdx::CheckCommand("file[%s] exists, overwrite or not ? (y/n)", path::name(path).c_str()));
				}

				TextFile out;

				if (out.open(path, true))
				{
					out.puts(GetTemplateConfigString());

					puts("export template config success");
				}
				else
				{
					puts("export template config failed");
				}
			}
			else
			{
				puts(GetTemplateConfigString().c_str());
			}

			return true;
		}

		if ((path = GetCmdParam(1)) == NULL || *path == 0)
		{
			puts("please input configure filename");

			return false;
		}

		if (init(path))
		{
			if (GetCmdParam("-l")) LogThread::Instance()->setLogFlag(2);

			process();

			clean();

			return true;
		}
		else
		{
			puts("initialize server failed");

			return false;
		}
	}
	void clean()
	{
		LogThread::Instance()->wait();
	}
	bool process()
	{
		if (sslenable)
		{
			context = SSLContext::GetGlobalContext();

			if (context->setCertificate(certfile))
			{
				LogTrace(eIMP, "load certificate[%s] success", certfile.c_str());
			}
			else
			{
				LogTrace(eERR, "load certificate[%s] failed", certfile.c_str());

				return false;
			}

			if (context->setCertPrivateKey(keyfile))
			{
				LogTrace(eIMP, "load privatekey[%s] sussess", keyfile.c_str());
			}
			else
			{
				LogTrace(eERR, "load privatekey[%s] failed", keyfile.c_str());

				return false;
			}
			
			context->setClientVerify(false);

			if (chipherlst.length() > 0) context->setCipherList(chipherlst);

			SSL_CTX_load_verify_locations(context->getHandle(), certfile.c_str(), NULL);
			SSL_CTX_set_options(context->getHandle(), SSL_OP_CIPHER_SERVER_PREFERENCE);

#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_1_0_2
			SSL_CTX_set_alpn_select_cb(context->getHandle(), SSLContext::SelectALPN, NULL);
			SSL_CTX_set_options(context->getHandle(), SSL_OP_SINGLE_ECDH_USE);
			SSL_CTX_set_ecdh_auto(context->getHandle(), 1);
#endif
		}

		TaskQueue::Instance()->start();

		ServerSocketSetConnectFunction(ProcessConnect);
		ServerSocketSetProcessFunction(ProcessRequest);
		ServerSocketSetConnectClosedFunction(ProcessConnectClosed);

		LogTrace(eINF, "enter process loop ...");
	
		ServerSocketLoop(host.c_str(), port, 100, timeout);

		LogThread::Instance()->setLogFlag(2);

		LogTrace(eERR, "listen host[%s][%d] failed[%s]", host.c_str(), port, GetErrorString().c_str());

		LogTrace(eERR, "process exiting ...");

		return false;
	}
};

START_APP(MainApplication)
